﻿using System;
using System.Web;
using System.Text;
using System.Web.UI;
using System.Globalization;
using System.Collections.Generic;
using System.Security.Permissions;
using System.Web.Script.Serialization;
using System.Reflection;

namespace JQueryControlExtenders.BaseExtender
{
    /// <summary>
    /// Similar to ScriptComponentDescriptor which provides a wrapper for converting server components into client script for ASP.NET AJAX behaviours,
    /// this ScriptControlDescriptor converts server objects into JQuery client objects.
    /// </summary>
    [AspNetHostingPermission(SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal), AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    public class JQueryScriptControlDescriptor : ScriptDescriptor
    {
        // Fields
        private string _elementIDInternal;
        private string _id;
        private SortedList<string, Expression> _properties;
        private SortedList<string, string> _events;
        private JavaScriptSerializer _serializer;
        private string _type;
        private string _initializationString;

        #region Properties
        public virtual string ClientID
        {
            get
            {
                return ID;
            }
        }

        internal string ElementIDInternal
        {
            get
            {
                return _elementIDInternal;
            }
        }

        public virtual string ID
        {
            get
            {
                return (_id ?? string.Empty);
            }
            set
            {
                _id = value;
            }
        }

        public virtual string InitializationString
        {
            get
            {
                return _initializationString ?? string.Empty;
            }
            set
            {
                _initializationString = value;
            }
        }

        private SortedList<string, Expression> Properties
        {
            get
            {
                if (_properties == null)
                {
                    _properties = new SortedList<string, Expression>(StringComparer.Ordinal);
                }
                return _properties;
            }
        }

        private SortedList<string, string> Events
        {
            get
            {
                if (this._events == null)
                {
                    this._events = new SortedList<string, string>(StringComparer.Ordinal);
                }
                return this._events;
            }
        }

        private JavaScriptSerializer Serializer
        {
            get
            {
                if (_serializer == null)
                {
                    _serializer = new JavaScriptSerializer();
                }
                return _serializer;
            }
        }

        public string Type
        {
            get
            {
                return _type;
            }
            set
            {
                if (string.IsNullOrEmpty(value))
                {
                    throw new ArgumentException("NullOrEmpty", "value");
                }
                _type = value;
            }
        }

        public string Suffix { get; set; }
        #endregion

        public JQueryScriptControlDescriptor(string type, string elementID)
        {
            if (string.IsNullOrEmpty(elementID))
            {
                throw new ArgumentException("NullOrEmpty", "elementID");
            }
            _elementIDInternal = elementID;
            _type = type;

        }

        public void AddProperty(string name, object value)
        {
            AddProperty(name, (Expression)new ObjectReference(value));
        }

        private void AddProperty(string name, Expression value)
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentException("NullOrEmpty", "name");
            }
            Properties[name] = value;
        }

        public void AddEvent(string name, string handler)
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentException("NullOrEmpty", "name");
            }
            if (string.IsNullOrEmpty(handler))
            {
                throw new ArgumentException("NullOrEmpty", "handler");
            }
            this.Events[name] = handler;
        }


        protected override string GetScript()
        {
            if (!string.IsNullOrEmpty(this.ID))
            {
                this.AddProperty("id", this.ID);
            }

            if (string.IsNullOrEmpty(this.ElementIDInternal))
            {
                throw new ArgumentException("Target Object can not be null");
            }

            StringBuilder builder = new StringBuilder();
            builder.Append("$(\"#");
            builder.Append(JavaScriptString.QuoteString(this.ElementIDInternal));
            builder.Append("\").");
            builder.Append(Type);
            builder.Append("(");
            if (!string.IsNullOrEmpty(InitializationString))
            {
                builder.Append("\"");
                builder.Append(JavaScriptString.QuoteString(InitializationString));
                builder.Append("\"");
            }
            if (((_properties != null) && (_properties.Count > 0)) ||
                 ((_events != null) && (_events.Count > 0)))
            {
                if (!string.IsNullOrEmpty(InitializationString))
                    builder.Append(",");

                builder.Append("{");
                bool renderedItems = AppendPropertiesScript(builder);

                if (renderedItems && _events != null && _events.Count > 0)
                    builder.Append(",");

                AppendEventsScript(builder);
                builder.Append("}");
            }

            builder.Append(")");
            if (!string.IsNullOrEmpty(Suffix))
                builder.Append(Suffix);
            builder.Append(";");

            return builder.ToString();

        }

        private bool AppendPropertiesScript(StringBuilder builder)
        {
            bool renderedElements = false;
            if ((_properties != null) && (_properties.Count > 0))
            {
                foreach (KeyValuePair<string, Expression> pair in _properties)
                {
                    if (pair.Value.Type != ExpressionType.Script)
                    {
                        continue;
                    }
                    if (!renderedElements)
                    {
                        renderedElements = true;
                    }
                    else
                    {
                        builder.Append(",");
                    }
                    builder.Append(pair.Key);
                    builder.Append(':');
                    pair.Value.AppendValue(this.Serializer, builder);
                }
            }
            return renderedElements;
        }

        private bool AppendEventsScript(StringBuilder builder)
        {
            bool renderedElements = false;
            if ((_events != null) && (_events.Count > 0))
            {
                foreach (KeyValuePair<string, string> pair in _events)
                {
                    if (!renderedElements)
                    {
                        renderedElements = true;
                    }
                    else
                    {
                        builder.Append(',');
                    }
                    builder.Append('"');
                    builder.Append(JavaScriptString.QuoteString(pair.Key));
                    builder.Append('"');
                    builder.Append(':');
                    builder.Append(pair.Value);
                }
            }
            return renderedElements;
        }

        public static string SerializeAsJavaScript(object val)
        {
            StringBuilder blder = new StringBuilder();
            ObjectReference obj = new ObjectReference(val);
            obj.AppendValue(new JavaScriptSerializer(), blder);
            return blder.ToString();
        }

        #region Internal Classes
        protected enum ExpressionType
        {
            Script,
            ComponentReference
        }

        protected abstract class Expression
        {
            // Methods
            protected Expression() { }
            public abstract void AppendValue(JavaScriptSerializer serializer, StringBuilder builder);

            // Properties
            public abstract ExpressionType Type { get; }
        }

        protected sealed class ObjectReference : Expression
        {
            // Fields
            private object _value;
            static Type JavaScriptSerializerType = typeof(JavaScriptSerializer);
            static Type serializationFormatEnum = JavaScriptSerializerType.GetNestedType("SerializationFormat", BindingFlags.NonPublic);
            static MethodInfo serializeInternal = JavaScriptSerializerType.GetMethod("Serialize", BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(object), typeof(StringBuilder), serializationFormatEnum }, null);
            static object javaScriptEnumValue = Enum.Parse(serializationFormatEnum, "JavaScript");
            // Methods
            public ObjectReference(object value)
            {
                this._value = value;
            }

            public override void AppendValue(JavaScriptSerializer serializer, StringBuilder builder)
            {
                serializeInternal.Invoke(serializer, new object[] { this._value, builder, javaScriptEnumValue });
                //serializer.Serialize(this._value, builder); //, JavaScriptSerializer.SerializationFormat.JavaScript);
            }

            // Properties
            public override ExpressionType Type
            {
                get
                {
                    return ExpressionType.Script;
                }
            }
        }

        internal class JavaScriptString
        {
            // Fields
            private int _index;
            private string _s;

            // Methods
            internal JavaScriptString(string s)
            {
                this._s = s;
            }

            private static void AppendCharAsUnicode(StringBuilder builder, char c)
            {
                builder.Append(@"\u");
                builder.AppendFormat(CultureInfo.InvariantCulture, "{0:x4}", new object[] { (int)c });
            }

            internal string GetDebugString(string message)
            {
                return string.Concat(new object[] { message, " (", this._index, "): ", this._s });
            }

            internal char? GetNextNonEmptyChar()
            {
                while (this._s.Length > this._index)
                {
                    char c = this._s[this._index++];
                    if (!char.IsWhiteSpace(c))
                    {
                        return new char?(c);
                    }
                }
                return null;
            }

            internal char? MoveNext()
            {
                if (this._s.Length > this._index)
                {
                    return new char?(this._s[this._index++]);
                }
                return null;
            }

            internal string MoveNext(int count)
            {
                if (this._s.Length >= (this._index + count))
                {
                    string str = this._s.Substring(this._index, count);
                    this._index += count;
                    return str;
                }
                return null;
            }

            internal void MovePrev()
            {
                if (this._index > 0)
                {
                    this._index--;
                }
            }

            internal void MovePrev(int count)
            {
                while ((this._index > 0) && (count > 0))
                {
                    this._index--;
                    count--;
                }
            }

            internal static string QuoteString(string value)
            {
                StringBuilder builder = null;
                if (string.IsNullOrEmpty(value))
                {
                    return string.Empty;
                }
                int startIndex = 0;
                int count = 0;
                for (int i = 0; i < value.Length; i++)
                {
                    char c = value[i];
                    if ((((c == '\r') || (c == '\t')) || ((c == '"') || (c == '\''))) || ((((c == '<') || (c == '>')) || ((c == '\\') || (c == '\n'))) || (((c == '\b') || (c == '\f')) || (c < ' '))))
                    {
                        if (builder == null)
                        {
                            builder = new StringBuilder(value.Length + 5);
                        }
                        if (count > 0)
                        {
                            builder.Append(value, startIndex, count);
                        }
                        startIndex = i + 1;
                        count = 0;
                    }
                    switch (c)
                    {
                        case '<':
                        case '>':
                        case '\'':
                            {
                                AppendCharAsUnicode(builder, c);
                                continue;
                            }
                        case '\\':
                            {
                                builder.Append(@"\\");
                                continue;
                            }
                        case '\b':
                            {
                                builder.Append(@"\b");
                                continue;
                            }
                        case '\t':
                            {
                                builder.Append(@"\t");
                                continue;
                            }
                        case '\n':
                            {
                                builder.Append(@"\n");
                                continue;
                            }
                        case '\f':
                            {
                                builder.Append(@"\f");
                                continue;
                            }
                        case '\r':
                            {
                                builder.Append(@"\r");
                                continue;
                            }
                        case '"':
                            {
                                builder.Append("\\\"");
                                continue;
                            }
                    }
                    if (c < ' ')
                    {
                        AppendCharAsUnicode(builder, c);
                    }
                    else
                    {
                        count++;
                    }
                }
                if (builder == null)
                {
                    return value;
                }
                if (count > 0)
                {
                    builder.Append(value, startIndex, count);
                }
                return builder.ToString();
            }

            internal static string QuoteString(string value, bool addQuotes)
            {
                string str = QuoteString(value);
                if (addQuotes)
                {
                    str = "\"" + str + "\"";
                }
                return str;
            }

            public override string ToString()
            {
                if (this._s.Length > this._index)
                {
                    return this._s.Substring(this._index);
                }
                return string.Empty;
            }
        }
        #endregion
    }
}
